<?php
/**
 * Created by PhpStorm.
 * User: kb130
 * Date: 2017/8/18
 * Time: 15:24
 */

namespace util\validate;

use util\validate\rules\BaseRule;

/**
 * 数据验证器
 *
 * Class Validator
 * @package validate
 */
class Validator
{
    const OCCUPIED = ':attribute';

    protected $messageConfig;

    protected $messages = [];

    protected $params = [];

    protected $initialRules;

    protected $rules;

    const SPECIAL_RULES = 'sameAs';

    /**
     * 验证执行
     *
     * @param array $data
     * @param array $rules
     * @return $this
     */
    public function verify(array $data, array $rules)
    {
        $this->initialRules = $rules;
        $this->messageConfig = include('validator_message.php');
        $this->parseData($data);

        return $this;
    }

    /**
     * 获取错误的具体信息
     *
     * @param null $key
     * @return array|mixed
     */
    public function getErrors($key = null)
    {
        if (is_null($key)) {
            return $this->messages;
        }

        return $this->messages[$key];
    }

    /**
     * 判断是否有错误
     *
     * @return bool
     */
    public function hasError()
    {
        return !empty($this->messages) ? true : false;
    }

    /**
     * 获取第一条错误信息
     *
     * @return string
     */
    public function firstError($addField = true)
    {
        $key = '';

        if (!empty($this->messages)) {
            if ($addField) {
                $key = array_keys($this->messages)[0];
            }

            $curr = current($this->messages);
            $message = false !== strpos($curr[0], $key) ? $curr[0] : $key . $curr[0];
            return $message;
        }

        return '';
    }

    /**
     * 把规则解析，并调用规则的验证方法
     *
     * @param array $data
     */
    private function parseData(array $data)
    {
        $verifyKey = array_keys($this->initialRules);

        foreach ($verifyKey as $item) {
            $rule = $this->initialRules[$item];

            foreach ($rule as $key => $value) {
                $methodName = $this->convertUnderline($this->parseRuleMethod($value[0]));
                $reqData = $this->parseReqDate($data, $item);
                $ruleObj = empty($this->params) ? Rule::$methodName($reqData) : Rule::$methodName($reqData, $this->params);

                if (0 === strcmp('requiredOr', $methodName)) {
                    $this->params = $this->parseReqDate($data, $this->params);

                    $ruleObj = Rule::$methodName($reqData, $this->params);
                    $bool = $ruleObj->verify();

                    if (!$bool) {
                        $this->parseMessage($value, $item, $methodName);
                    }
                } else if (0 === strcmp('required', $methodName)) {
                    $bool = $ruleObj->verify();

                    if (!$bool) {
                        $this->parseMessage($value, $item, $methodName);
                    }
                } else if (isset($reqData) && '' !== $reqData) {
                    if (0 === strcmp($methodName, static::SPECIAL_RULES)) {
                        $this->params = $this->parseReqDate($data, $this->params);
                        $ruleObj = Rule::$methodName($reqData, $this->params);

                        $bool = $ruleObj->verify();

                        if (!$bool) {
                            $this->parseMessage($value, $item, $methodName);
                        }
                    }

                    if ($ruleObj instanceof BaseRule) {
                        $bool = $ruleObj->verify();

                        if (!$bool) {
                            $this->parseMessage($value, $item, $methodName);
                        }
                    }
                } else {
                    break;
                }

            }
        }
    }

    /**
     * 获取规则名称
     *
     * @param $value
     * @return array|string
     */
    private function parseRuleMethod($value)
    {
        $method = trim($value);
        $this->params = [];

        if (false === strpos($method,':')) {
            return $method;
        }

        $method = explode(':', $method);
        $this->params = $this->parseRuleParams($method[1]);

        return trim($method[0]);
    }

    /**
     * 解析规则中的参数值
     * 如果是多个,分割，则转换成数组，否则就是字符串。
     *
     * @param $value
     * @return array
     */
    private function parseRuleParams($value)
    {
        if (false !== strpos($value,',')) {
            $value = explode(',', $value);

            if (is_array($value)) {
                $value = $this->trimArr($value);
            }
        }

        return $value;
    }

    /**
     * 去掉数组参数中的空格
     *
     * @param $arr
     * @return array
     */
    private function trimArr($arr)
    {
        $retArr = [];

        if (!empty($arr)) {
            foreach ($arr as $item) {
                $retArr[] = trim($item);
            }
        }

        return $retArr;
    }

    /**
     * 下划线命名转换为驼峰命名
     *
     * @param $str
     * @param bool $ucfirst
     * @return mixed|string
     */
    private function convertUnderline ( $str , $ucfirst = false)
    {
        $str = ucwords(str_replace('_', ' ', $str));
        $str = str_replace(' ','',lcfirst($str));

        return $ucfirst ? ucfirst($str) : $str;
    }

    /**
     * 解析错误信息
     *
     * @param $data
     * @param $key
     */
    private function parseMessage($data, $key, $type)
    {
        $message = $this->messageConfig[$type];

        if (isset($data[1])&& 0 !== strcmp('', $data[1])) {
            $this->messages[$key][] = $data[1];
        } else {
            $this->messages[$key][] = str_replace(static::OCCUPIED, $key, $message);
        }
    }

    /**
     * 解析数组嵌套的key，对应的值。
     *
     * @param $data
     * @param $key
     * @return string
     */
    private function parseReqDate($data, $key)
    {
        preg_match_all("/([\w]*?)\[([\s\S]*?)\]/",$key, $mat);

        if (isset($mat[2]) && !empty($mat[2])) {
            return count($mat[2]) == 1 ? $data[$mat[1][0]][$mat[2][0]] : $data[$mat[1][0]][$mat[2][0]][$mat[2][1]];
        }

        return isset($data[$key]) ? $data[$key] : '';
    }

}